package com.ponhu.ea2.stockcenter.provider.support;

import com.alibaba.fastjson.JSON;
import com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum;
import com.ponhu.ea2.stockcenter.provider.support.exception.StockRuntimeException;
import com.ponhu.ea2.stockcenter.provider.constants.CommonConstant;
import com.ponhu.ea2.stockcenter.provider.support.service.RedisSupportService;
import com.ponhu.ea2.stockcenter.provider.utils.RedisUtils;
import com.ponhu.ea2.stockcenter.vo.ReturnResponseBean;
import io.netty.util.concurrent.FastThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.lang.reflect.Type;
import java.time.LocalTime;
import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Order(2)
@Component
public class IdempotenceAop {

    @Autowired
    private FastThreadLocal<String> traceIdLocal;

    @Autowired
    private RedisSupportService redisSupportService;

    @Pointcut("@annotation(com.ponhu.ea2.stockcenter.provider.decorators.Idempotence)")
    private void annotationAspect() {
    }

    @Around("annotationAspect()")
    public Object doAroundService(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object[] args;
        try {
            args = proceedingJoinPoint.getArgs();

            //方法参数校验
            if (args == null || args.length < 1 || !(args[0] instanceof String)) {
                throw new StockRuntimeException(ResponseCodeEnum.IDEMPOTENCE_USED_ERROR_900004);
            }

            String stockRequestToken = args[0].toString();

            if (StringUtils.isBlank(stockRequestToken) || !StringUtils.contains(stockRequestToken, ":")) {
                throw new StockRuntimeException(ResponseCodeEnum.IDEMPOTENCE_TOKEN_ERROR_900005);
            }

            // 入参token是在Redis连接异常情况下获取的，则直接去执行业务方法
            if (stockRequestToken.split(":")[1].equals(CommonConstant.IDEMPOTENCE_UUID)) {
                log.error("幂等性校验 ===>> [{}] ===>> Redis连接异常，直接执行业务方法", traceIdLocal.get());
                return proceedingJoinPoint.proceed();
            } else { // 否则正常先执行幂等性校验，再执行业务方法
                return proceedWithRedis(proceedingJoinPoint, stockRequestToken);
            }
        } catch (Throwable throwable) {
            // 如果事务处在活跃状态，那么事务回滚
            if (TransactionSynchronizationManager.isActualTransactionActive()) {
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }

            throw throwable;
        }
    }

    public Object proceedWithRedis(ProceedingJoinPoint proceedingJoinPoint, String stockRequestToken) throws Throwable {

        String redisKey = RedisUtils.getRedisKey(CommonConstant.IDEMPOTENCE_PREFIX, stockRequestToken);
        // 根据请求参数的token获取Redis幂等表中缓存值
        if (BooleanUtils.isNotTrue(redisSupportService.bucketIsExists(redisKey))) { // 当前请求token已失效
            log.error("幂等性校验 ===>> [{}] ===>> 校验Token失效 ===>> token={}", traceIdLocal.get(), stockRequestToken);
            return ReturnResponseBean.error(ResponseCodeEnum.GET_TOKEN_EXCEPTION_900002);
        }

        Object result; // 业务方法执行结果
        String rSemaphoreKey = RedisUtils.getRedisKey(CommonConstant.IDEMPOTENCE_SEMAPHORE, stockRequestToken);
        String permitId = redisSupportService.semaphoreAcquire(rSemaphoreKey);// 获取信号量，无法立即获取则阻塞等待到获取
        String redisValue = redisSupportService.bucketGet(redisKey, String.class);
        if (StringUtils.isBlank(redisValue) || redisKey.equals(redisValue)) {
            // 幂等值校验通过，说明是第一次请求，去执行业务方法
            log.info("幂等性校验 ===>> [{}] ===>> 幂等值校验通过 ===>> token={}", traceIdLocal.get(), stockRequestToken);
            result = proceedingJoinPoint.proceed();
            // 业务方法执行完后将结果值缓存至幂等表
            redisSupportService.bucketSet(redisKey, JSON.toJSONString(result),
                    CommonConstant.IDEMPOTENCE_EXPIRED_SECONDS, TimeUnit.SECONDS);
        } else {
            // 幂等值校验未通过，说明是不是第一次请求，直接将幂等表中缓存的业务结果值取出并返回
            log.error("幂等性校验 ===>> [{}] ===>> 幂等值校验命中 ===>> token={}", traceIdLocal.get(), stockRequestToken);
            MethodSignature sign = (MethodSignature) proceedingJoinPoint.getSignature();
            Type genericReturnType = sign.getMethod().getGenericReturnType();
            result = JSON.parseObject(redisValue, genericReturnType);
        }
        if (StringUtils.isNotEmpty(permitId)) redisSupportService.semaphoreRelease(rSemaphoreKey, permitId); // 释放信号量

        return result;
    }

}
